package it.polimi.jita.cp.block.scheduler;

import it.polimi.jita.cp.block.Block;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Paint;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.csv.CSVRecord;
import org.apache.commons.math3.fraction.BigFraction;
import org.apache.commons.math3.util.ArithmeticUtils;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.SubCategoryAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.GroupedStackedBarRenderer;
import org.jfree.data.KeyToGroupMap;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.ui.GradientPaintTransformType;
import org.jfree.ui.StandardGradientPaintTransformer;

public class Scheduler extends Block<SchedulerInput, SchedulerOutput> {

	private static final int PRECISION = 1000;

	@Override
	protected SchedulerOutput computeOutput() {
		Set<IVirtualMachineSchedulerData> vms = getInput().getVirtualMachines();

		Integer systemFrequency = getInput().getSystemCapacity();

		Integer lcmShares = getLCMShares(vms);
		Integer maxDenominator = ArithmeticUtils.lcm(systemFrequency, lcmShares
				* vms.size());
		if (maxDenominator < PRECISION) {
			maxDenominator = ArithmeticUtils.lcm(PRECISION,
					lcmShares * vms.size());
		}
		BigFraction systemFrequencyToDivided = new BigFraction(getInput()
				.getSystemCapacity());

		Map<IVirtualMachineSchedulerData, BigFraction> allocations = new HashMap<>();
		Map<IVirtualMachineSchedulerData, BigFraction> demands = new HashMap<>();
		Set<IVirtualMachineSchedulerData> vmsNeedFreq = new HashSet<>();
		BigFraction totShares = BigFraction.ZERO;

		// Assign reservation
		for (IVirtualMachineSchedulerData vm : vms) {
			double demand = vm.getDemand();
			double reservation = vm.getReservation();
			BigFraction allocated;
			if (demand > reservation) {
				allocated = new BigFraction(reservation, maxDenominator);
				vmsNeedFreq.add(vm);
				totShares = totShares.add(vm.getShares());
			} else {
				allocated = new BigFraction(demand, maxDenominator);
			}
			systemFrequencyToDivided = systemFrequencyToDivided
					.subtract(allocated);
			allocations.put(vm, allocated);
			demands.put(vm, new BigFraction(demand, maxDenominator));
		}

		while (!vmsNeedFreq.isEmpty()
				&& systemFrequencyToDivided.compareTo(BigFraction.ZERO) != 0) {

			BigFraction newTotShares = BigFraction.ZERO;
			BigFraction newSystemFrequencyToDivided = systemFrequencyToDivided;
			for (Iterator<IVirtualMachineSchedulerData> vmsNeedFreqIterator = vmsNeedFreq
					.iterator(); vmsNeedFreqIterator.hasNext();) {
				IVirtualMachineSchedulerData vm = vmsNeedFreqIterator.next();

				BigFraction capacityToSum = systemFrequencyToDivided.multiply(
						vm.getShares()).divide(totShares);
				BigFraction newAllocation = allocations.get(vm).add(
						capacityToSum);
				BigFraction demand = demands.get(vm);
				BigFraction allocated;
				if (newAllocation.compareTo(demand) > 0) {
					allocated = demand;
					vmsNeedFreqIterator.remove();
					newSystemFrequencyToDivided = newSystemFrequencyToDivided
							.subtract(demand.subtract(allocations.get(vm)));
				} else {
					allocated = newAllocation;
					newTotShares = newTotShares.add(vm.getShares());
					newSystemFrequencyToDivided = newSystemFrequencyToDivided
							.subtract(capacityToSum);
				}

				allocations.put(vm, allocated);
			}

			totShares = newTotShares;
			systemFrequencyToDivided = newSystemFrequencyToDivided;
		}

		for (Entry<IVirtualMachineSchedulerData, BigFraction> e : allocations
				.entrySet()) {
			e.getKey().setRun(e.getValue().doubleValue());
		}
		// estimateMaxBlockValue(vms);

		SchedulerOutput output = new SchedulerOutput();
		output.addAll(vms);
		return output;
	}

	// private double estimateMaxBlockValue(Set<IVirtualMachineSchedulerData>
	// vms) {
	// double meanDemand = 0d;
	// for (IVirtualMachineSchedulerData iVirtualMachineSchedulerData : vms) {
	// meanDemand = iVirtualMachineSchedulerData.getDemand() / vms.size();
	// }
	//
	// return meanDemand;
	// }

	private Integer getLCMShares(Collection<IVirtualMachineSchedulerData> vms) {
		Iterator<IVirtualMachineSchedulerData> vmsIterator = vms.iterator();
		int gcd = vmsIterator.next().getShares();
		while (vmsIterator.hasNext()) {
			gcd = ArithmeticUtils.gcd(gcd, vmsIterator.next().getShares());
		}

		vmsIterator = vms.iterator();
		int maxDenominator = vmsIterator.next().getShares() / gcd;
		while (vmsIterator.hasNext()) {
			maxDenominator = ArithmeticUtils.lcm(maxDenominator, vmsIterator
					.next().getShares() / gcd);
		}
		return maxDenominator;
	}

	@Override
	protected Set<JFreeChart> createCharts() {
		Set<JFreeChart> set = new HashSet<>();
		set.add(createPieChart());
		set.add(createBarChart());
		return set;
	}

	private JFreeChart createPieChart() {
		DefaultPieDataset dataset = new DefaultPieDataset();
		Collection<IVirtualMachineSchedulerData> vms = getOutput()
				.getVirtualMachines();
		double systemCapacity = getInput().getSystemCapacity();
		double free = systemCapacity;
		for (IVirtualMachineSchedulerData vm : vms) {
			dataset.setValue(vm.getName(), Math.round(vm.getRun()));
			free -= vm.getRun();
		}

		if (free / systemCapacity > 0.001)
			dataset.setValue("Free", free);

		JFreeChart chart = ChartFactory.createPieChart("Resource allocation",
		// Title
				dataset,
				// Dataset
				true,
				// Show legend
				true,
				// Use tooltips
				false);
		return chart;
	}

	private JFreeChart createBarChart() {
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		Collection<IVirtualMachineSchedulerData> vms = getOutput()
				.getVirtualMachines();
		SubCategoryAxis domainAxis = new SubCategoryAxis("Virtual machines");

		GroupedStackedBarRenderer renderer = new GroupedStackedBarRenderer();
		KeyToGroupMap map = new KeyToGroupMap("G1");

		for (IVirtualMachineSchedulerData vm : vms) {
			String name = vm.getName();
			Long allocation = Math.round(vm.getRun());
			Long request = Math.round(vm.getDemand());

			dataset.setValue(allocation, name + " allocation", name);
			dataset.setValue(request - allocation, name + " request", name);
		}

		renderer.setSeriesToGroupMap(map);

		renderer.setItemMargin(0.0);
		Paint p1 = new GradientPaint(0.0f, 0.0f, new Color(0x22, 0x22, 0xFF),
				0.0f, 0.0f, new Color(0x88, 0x88, 0xFF));

		Paint p2 = new GradientPaint(0.0f, 0.0f, new Color(0x22, 0xFF, 0x22),
				0.0f, 0.0f, new Color(0x88, 0xFF, 0x88));

		for (int i = 0; i < vms.size(); i++) {
			renderer.setSeriesPaint(i * 2, p1);
			renderer.setSeriesPaint(i * 2 + 1, p2);
		}

		renderer.setGradientPaintTransformer(new StandardGradientPaintTransformer(
				GradientPaintTransformType.HORIZONTAL));

		JFreeChart chart = ChartFactory.createBarChart(
				"Comparison between allocated and requested resource",
				// Title
				"Resource", "Resource", dataset, PlotOrientation.VERTICAL,
				// Dataset
				false,
				// Show legend
				true,
				// Use tooltips
				false);

		CategoryPlot plot = (CategoryPlot) chart.getPlot();
		plot.setDomainAxis(domainAxis);
		plot.setRenderer(renderer);

		return chart;
	}

	@Override
	protected Map<List<CSVRecord>, CSVRecord> createCSVFiles() {
		// TODO Auto-generated method stub
		return null;
	}

}
